﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

using Spencen.Mobile.UI.Transforms;

namespace Spencen.Mobile.UI
{
    public class GraphicsContext
    {
        private static Pen _focusPen;
        private float _opacity;
        protected Point RenderCenter { get; private set; }
        protected IList<Transform> Transforms { get; private set; }

        public GraphicsContext( GraphicsContext parentContext, DrawingElement drawingElement ) : this( parentContext.Graphics, drawingElement )
        {
            // Copy all the parent's transforms (to be applied AFTER child transforms)
            foreach ( var transform in parentContext.Transforms )
                Transforms.Add( transform );
        }

        public GraphicsContext( Graphics graphics, DrawingElement drawingElement ) : this( graphics )
        {
            RenderCenter = new Point(
                (int)( drawingElement.Size.Width * RenderOrigin.X ),
                (int)( drawingElement.Size.Height * RenderOrigin.Y ) );

            AddElementTransforms( drawingElement );
        }

        private void AddElementTransforms( DrawingElement drawingElement )
        {
            // Convert the properties into transforms
            // TODO: Cater for existing transforms - e.g. where both Angle and RotateTransform have been defined
            if ( drawingElement.Position != Point.Empty )
                Transforms.Insert( 0, new TranslateTransform( drawingElement.Position ) );
            //if ( frameworkElement.ScaleX != 1 || frameworkElement.ScaleY != 1 )
            //    Transforms.Insert( 0, new ScaleTransform( frameworkElement.ScaleX, frameworkElement.ScaleY ) );
            if ( drawingElement.Angle != 0 && drawingElement.SupportsRotation )
                Transforms.Insert( 0, new RotateTransform( drawingElement.Angle ) { RenderCenter = RenderCenter } );

            foreach ( var transform in drawingElement.Transforms.Reverse() )
            {
                Transforms.Insert( 0, transform );
                transform.RenderCenter = RenderCenter;
            }
        }

        public GraphicsContext( DrawingElement drawingElement ) : this( (Graphics) null )
        {
            RenderCenter = new Point(
                (int)( drawingElement.Size.Width * RenderOrigin.X ),
                (int)( drawingElement.Size.Height * RenderOrigin.Y ) );

            GetTransformsForParent( drawingElement );
        }

        private void GetTransformsForParent( DrawingElement parentElement )
        {
            // Recurse to root then add transforms
            if ( parentElement.Parent != null )
                GetTransformsForParent( parentElement.Parent );

            AddElementTransforms( parentElement );
        }

        public GraphicsContext( Graphics graphics )
        {
            Transforms = new List<Transform>();
            Graphics = graphics;
            RenderOrigin = new PointF( 0.5f, 0.5f );
            if ( graphics != null )
                ClipBounds = new Rectangle( (int) graphics.ClipBounds.X, (int) graphics.ClipBounds.Y, (int) graphics.ClipBounds.Width, (int) graphics.ClipBounds.Height );
        }

        public Graphics Graphics { get; private set; }
        public Rectangle ClipBounds { get; private set; }

        public Pen FocusPen
        {
            get
            {
                if ( _focusPen == null )
                    _focusPen = new Pen( Color.Red );
                return _focusPen;
            }
        }

        public float Opacity
        {
            get { return _opacity; }
            set { _opacity = Math.Max( Math.Min( 1.0f, value ), 0.0f ); }
        }

        public bool IsScaled
        {
            get { return Transforms.OfType<ScaleTransform>().Any( st => st.ScaleX != 1.0 || st.ScaleY != 1.0 ); }
        }

        public PointF RenderOrigin { get; set; }

        public Point[] TransformedPoints( Point[] points )
        {
            var transformedPoints = points;

            // Now apply any transforms that have been "inherited" by parent objects.
            // These are applied in reverse order, imagine the scenario:
            // Parent Panel is rotated 45 degrees then translated 100 pixels left.
            // Child item is rotated 15 degrees.
            // So execution must be:
            // Rotate child 15 degrees (by default around it own center).
            // Rotate child 45 degrees around the center of the parent panel.
            // Translate child 100 pixels left
            foreach ( var transform in Transforms )
                transformedPoints = transform.TransformedPoints( transformedPoints, transform.RenderCenter );

            return transformedPoints;
        }

        public Point[] TransformedPointsReverse( Point[] transformedPoints )
        {
            var points = transformedPoints;

            // Now apply any transforms that have been "inherited" by parent objects.
            // These are applied in reverse order, imagine the scenario:
            // Parent Panel is rotated 45 degrees then translated 100 pixels left.
            // Child item is rotated 15 degrees.
            // So execution must be:
            // Rotate child 15 degrees (by default around it own center).
            // Rotate child 45 degrees around the center of the parent panel.
            // Translate child 100 pixels left
            foreach ( var transform in Transforms.Reverse() )
                points = transform.TransformedPointsReverse( points, transform.RenderCenter );

            return points;
        }
    }
}
